from typing import List

import jwt
import pendulum
import uuid
from starlette.authentication import (BaseUser, AuthenticationBackend, AuthCredentials, UnauthenticatedUser,
                                      AuthenticationError)
from starlette.requests import HTTPConnection, Request
from starlette.responses import PlainTextResponse
from redis_connection import redis_conn as redis
import logging
import config

# 这里在未来需要转换成使用配置完成部门归属定义
_root_departments = [17, 18, 19, 2, 3]
_develop_departments = [3]
_project_departments = [20, 21, 22, 23, 24, 26, 27, 28, 29, 30]


class AuthorizedUser(BaseUser):
    def __init__(self, open_id: str, display_name: str, belongs: int, leaders: bool, department_level: int,
                 area_flag: str = "0", from_enterprise: bool = False, from_public: bool = False,
                 jti: str = None, exp: int = None, iat: int = None, **kwargs) -> None:
        self._username = display_name
        self.open_id = open_id
        self.belongs = belongs
        self.leaders = leaders
        self.area_flag = area_flag
        self.department_level = department_level
        self._enterprise = from_enterprise
        self._public = from_public
        self.jti = jti if jti is not None else uuid.uuid4().hex
        self.exp = exp if exp is not None else pendulum.now().add(seconds=config.TOKEN_VALID_DURATION).int_timestamp
        self.iat = iat if iat is not None else pendulum.now().int_timestamp
        self.__dict__.update(kwargs)

    @property
    def display_name(self) -> str:
        return self._username

    @property
    def identity(self) -> List[str]:
        identity = []
        if self.is_authenticated:
            identity.append("authenticated")
        else:
            identity.append("unauthenticated")
        # TODO: 这里需要加入用户所属部门以及身份、权限等内容的判断
        return identity

    @property
    def is_authenticated(self) -> bool:
        now = pendulum.now()
        issue_at = pendulum.from_timestamp(self.iat)
        expires = pendulum.from_timestamp(self.exp)
        return issue_at <= now <= expires

    @property
    def is_leader(self) -> bool:
        return self.leaders

    @property
    def is_supervisor(self) -> bool:
        return self.leaders and self.belongs in _root_departments

    @property
    def from_enterprise(self) -> bool:
        return self._enterprise

    @property
    def from_public(self) -> bool:
        return self._public

    @property
    def is_developer(self) -> bool:
        return self.belongs in _develop_departments

    @classmethod
    def load_from_jwt(cls, token: str) -> 'AuthorizedUser':
        try:
            token_payload = jwt.decode(token, key=config.TOKEN_SECRET, algorithms='HS512')
        except jwt.DecodeError:
            raise TokenError("Token decode failed.")
        return AuthorizedUser(**token_payload)

    def as_dict(self):
        return {name: getattr(self, name)
                for name in dir(self)
                if not name.startswith('__')
                and not name.startswith('_')
                and name not in ["identity", "is_authenticated", "is_leader", "is_supervisor", "is_developer"]
                and not callable(getattr(self, name))}

    def to_jwt(self) -> str:
        payload = self.as_dict()
        return jwt.encode(payload, key=config.TOKEN_SECRET, algorithm='HS512')

    def refresh(self) -> None:
        self.exp = pendulum.now().add(seconds=config.TOKEN_VALID_DURATION).int_timestamp


class TokenError(Exception):
    def __init__(self, msg: str) -> None:
        self.message = msg


class JWTAuthenticationBackend(AuthenticationBackend):

    async def authenticate(self, request: HTTPConnection):
        try:
            if "Authorization" not in request.headers:
                raise TokenError("请求未携带授权数据。")

            auth_token = request.headers["Authorization"]
            schema, credentials = auth_token.split()
            if schema.lower() != "bearer":
                raise TokenError("使用了未受支持的授权协议。")
            user = AuthorizedUser.load_from_jwt(credentials)
            if redis.exists(user.open_id):
                token_from_redis = redis.get(user.open_id).decode()
                if not token_from_redis == credentials:
                    redis.delete(user.open_id)
                    raise TokenError("使用未登记令牌进行访问！")
        except TokenError as err:
            return AuthCredentials(["unauthenticated"]), UnauthenticatedUser()
        return AuthCredentials(user.identity), user


def on_auth_error(request: Request, exc: Exception):
    return PlainTextResponse(str(exc), status_code=403)
